package com.acme.bankapp.service.bank;

import com.acme.bankapp.domain.bank.Bank;
import com.acme.bankapp.domain.bank.account.Account;
import com.acme.bankapp.domain.bank.client.Client;
import com.acme.bankapp.domain.bank.client.Gender;
import com.acme.bankapp.domain.bank.exception.ClientExistsException;
import com.acme.bankapp.domain.bank.exception.DataIntegrityException;
import com.acme.bankapp.domain.bank.exception.NotEnoughFundsException;
import com.acme.bankapp.service.bank.listener.ClientRegistrationListener;
import com.acme.bankapp.service.bank.utils.ClientFactory;
import com.acme.bankapp.service.bank.utils.ClientInfoParser;
import com.acme.bankapp.service.bank.utils.ParseClientInfoException;

import java.io.*;

/**
 * This class for manage {@link Bank} object
 *
 * @author Alexander Kotov
 */

public class BankService {

    private final Bank bank;

    public BankService(Bank bank) {
        assert(bank != null);
        if (bank == null) {
            throw  new IllegalArgumentException("Null parameter bank");
        }
        this.bank = bank;
    }

    public BankService(BankLoader bankLoader) {
        this.bank =  bankLoader.getBank();
    }

    /**
     * @return bank clients
     */
    public Client[] getClients() {
        return bank.getClients();
    }
    /**
     * This method add client
     *
     * @param name    client's name
     * @param gender  client's gender
     * @param account client's account
     * @throws ClientExistsException
     */
    public void addClient(String name, Gender gender, Account account)
            throws ClientExistsException, DataIntegrityException {
        Client client = new Client(name, gender, account);
        account.connectWithClient(client);
        bank.addClient(client);
    }

    /**
     * This method withdraw and deposit random value on all bank clients accounts
     */
    public void modifyBank() {
        System.out.println("Bank modification");
        for (Client client : bank.getClients()) {
            withdrawFromClientAccount(client, Math.random());
            depositToClientAccount(client, Math.random());
        }
    }

    /**
     * This method print balance for all clients
     */
    public void printBalance() {
        for (Client client : bank.getClients()) {
            System.out.format("%s %s%n", client, client.getAccount());
        }
    }

    /**
     * @param client
     * @return account of client
     */
    public Account getAccountByClient(Client client) {
        for (Client c : bank.getClients()) {
            if (c.equals(client)) {
                return c.getAccount();
            }
        }
        return null;
    }

    /**
     * This method deposit amount on client's account
     *
     * @param client
     * @param amount
     */
    public void depositToClientAccount(Client client, double amount) {
        final Account accountByClient = getAccountByClient(client);
        if (accountByClient == null) {
            System.out.println(String.format("There is no account for client: %s", client));
            return;
        }
        accountByClient.deposit(amount);
    }

    /**
     * This method withdraw amount from client's account
     *
     * @param client
     * @param amount
     */
    public void withdrawFromClientAccount(Client client, double amount) {
        try {
            Account account = getAccountByClient(client);
            if (account != null) {
                account.withdraw(amount);
            }
        } catch (NotEnoughFundsException e) {
            System.out.println(e.getMessage());
        }
    }

    /**
     * @return all clients accounts
     */
    public Account[] getAccounts() {
        Client[] clients = bank.getClients();
        Account[] retAccounts = new Account[clients.length];
        for (int i = 0; i != clients.length; ++i) {
            retAccounts[i] = clients[i].getAccount();
        }
        return retAccounts;
    }

    /**
     * This method print maximum amount to withdraw for all clients
     */
    public void printMaxAmountToWithdraw() {
        for (Client client : bank.getClients()) {
            System.out.format("Maximum amount to withdraw for %s is %f%n",
                    client, client.getAccount().getMaximumAmountToWithdraw());
        }
    }

    /**
     * This method add client registration listener to bank
     *
     * @param listener listener to add
     */
    public void addClientRegistrationListener(ClientRegistrationListener listener) {
        bank.addClientRegistrationListener(listener);
    }

    /**
     * This method load info about bank clients from file and adds these clients to the bank
     *
     * @param fileName
     * @throws ParseClientInfoException
     * @throws ClientExistsException
     * @throws IOException
     */
    public void loadClientsFromFile(final String fileName)
            throws ParseClientInfoException, ClientExistsException, IOException {

        BufferedReader bufReader;
        String line;
        try {
            bufReader = new BufferedReader(new FileReader(fileName));
            while ((line = bufReader.readLine()) != null) {
                bank.addClient(ClientFactory.createClient(new ClientInfoParser(line)));
            }
        } catch (IOException e) {
            throw e;
        }
    }

    /**
     * This method serialize {@link Bank} object to file {@code fileName}
     *
     * @param fileName name of file to serialize
     * @throws IOException
     */
    public void saveBank(final String fileName)
            throws IOException {
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(fileName))) {
            oos.writeObject(bank);
        }
    }
}
